﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Schema;

namespace CodePlex.Resourceful.Microsoft.SSDS
{


  
    public class ErrorException : Exception
    {
        private readonly Error _error;
        public ErrorException(Error error) 
            :base(error.Message)
        {
            _error = error;
        }
        public Error Error { get { return _error; } }
       
    }
    public class Error
    {
        private readonly int _code;
        private readonly string _message;
        private Error(int code, string message)
        {
            _code = code;
            _message = message;
        }
        public int Code { get { return _code; } }
        public string Message { get { return _message; } }

        public static Error Parse(XmlReader reader)
        {
            string message = default(string);
            int code = default(int);

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.LocalName == "Message" && reader.NamespaceURI == SSDSConstants.Namespaces.Sitka)
                    {
                        message = Utils.ReadString(reader);
                    }
                    else if (reader.LocalName == "Code" && reader.NamespaceURI == SSDSConstants.Namespaces.Sitka)
                    {
                        code = int.Parse(Utils.ReadString(reader));
                    }
                }

                if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "Error" && reader.NamespaceURI == SSDSConstants.Namespaces.Sitka)
                {
                    return new Error(code, message);
                }

            }

            throw CodePlexException.Format("Failed to parse Error.");
        }



    }
    public class FlexibleProperty
    {
        private readonly XmlTypeCode _code;
        private readonly string _name;
        private readonly object _value;
        private FlexibleProperty(XmlTypeCode code, string name, object value)
        {
            _code = code;
            _name = name;
            _value = value;
        }
        public XmlTypeCode Code { get { return _code; } }
        public string Name { get { return _name; } }
        public object Value { get { return _value; } }

        public static FlexibleProperty String(string name, string value)
        {
            return new FlexibleProperty(XmlTypeCode.String, name,value);
        }

        public static FlexibleProperty Decimal(string name, decimal value)
        {
            return new FlexibleProperty(XmlTypeCode.Decimal, name, value);
        }

        public static FlexibleProperty Boolean(string name, bool value)
        {
            return new FlexibleProperty(XmlTypeCode.Boolean, name, value);
        }

        public static FlexibleProperty Binary(string name, byte[] value)
        {
            return new FlexibleProperty(XmlTypeCode.Base64Binary, name, value);
        }

        public static FlexibleProperty DateTime(string name, DateTime value)
        {
            return new FlexibleProperty(XmlTypeCode.DateTime, name, value);
        }



    }


    public class Entity
    {
        private readonly string _id;
        private readonly int _version;
        private readonly string _kind;
        private readonly FlexibleProperty[] _properties;
        private Entity(string id, int version, string kind, FlexibleProperty[] properties)
        {
            _id = id;
            _version = version;
            _kind = kind;
            _properties = properties;
        }
        public string ID { get { return _id; } }
        public int Version { get { return _version; } }
        public string Kind { get { return _kind; } }
        public IEnumerable<FlexibleProperty> Properties { get { return _properties; } }


        public static Entity Parse(XmlReader reader)
        {
            while (reader.Read())
            {
       
                if (reader.NodeType == XmlNodeType.Element && 
                    
                    (reader.NamespaceURI == string.Empty || (reader.NamespaceURI == SSDSConstants.Namespaces.Sitka && reader.LocalName == "Entity"))
                    
                    )
                {
                    return Entity.Parse(reader.LocalName, reader);
                }

            }
            throw CodePlexException.Format("Failed to parse Entity.");
        }
        public static Entity Parse(string kind, XmlReader reader)
        {

            string id = default(string);
            int version = default(int);
            List<FlexibleProperty> properties = new List<FlexibleProperty>();

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == kind )
                {
                    return new Entity(id, version, kind, properties.ToArray());
                }

                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.LocalName == "Id" && reader.NamespaceURI == SSDSConstants.Namespaces.Sitka)
                    {
                        id = Utils.ReadString(reader);
                    }
                    else if (reader.LocalName == "Version" && reader.NamespaceURI == SSDSConstants.Namespaces.Sitka)
                    {
                        version = int.Parse(Utils.ReadString(reader));
                    }
                    else
                    {
                        // flex
                        string name = reader.LocalName;
                        string xsiType = reader.GetAttribute("type", Namespaces.XMLSchemaInstance);
                        if (xsiType == "x:string")
                        {
                            string content = Utils.ReadString(reader);
                            properties.Add(FlexibleProperty.String(name, content));
                        }
                        if (xsiType == "x:decimal")
                        {
                            string content = Utils.ReadString(reader);
                            properties.Add(FlexibleProperty.Decimal(name, decimal.Parse(content)));
                        }
                        if (xsiType == "x:boolean")
                        {
                            string content = Utils.ReadString(reader);
                            properties.Add(FlexibleProperty.Boolean(name, bool.Parse(content)));
                        }
                        if (xsiType == "x:base64Binary")
                        {
                            string content = Utils.ReadString(reader);
                            byte[] bytes = Utils.FromBase64(content);
                            properties.Add(FlexibleProperty.Binary(name, bytes));
                        }
                        if (xsiType == "x:dateTime")
                        {
                            string content = Utils.ReadString(reader);
                            DateTime datetime = DateTime.Parse(content).ToLocalTime();
                            properties.Add(FlexibleProperty.DateTime(name,datetime));
                        }


                    }
                }



            }

            throw CodePlexException.Format("Failed to parse Entity.");
        }
    }

    public class Authority : IdAndVersion
    {
        public Authority(string id, int version)
            : base(id, version)
        {

        }
        public static Authority Parse(XmlReader reader)
        {
            return InternalParse<Authority>(reader, "Authority", delegate(string id, int version)
            {
                return new Authority(id, version);
            });
        }
    }
    public class Container : IdAndVersion
    {
        public Container(string id, int version)
            : base(id,version)
        {

        }
        public static Container Parse(XmlReader reader)
        {
            return InternalParse<Container>(reader,"Container",delegate(string id, int version) 
            {
                return new Container(id,version);
            });
        }
    }
    public class IdAndVersion
    {
        private readonly string _id;
        private readonly int _version;

        protected IdAndVersion(string id, int version)
        {
            _id = id;
            _version = version;
        }

        public string ID { get { return _id; } }
        public int Version { get { return _version; } }

        protected static T InternalParse<T>(XmlReader reader, string name, Func20<string, int, T> ctor)
        {
            string id = default(string);
            int version = default(int);

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.LocalName == "Id" && reader.NamespaceURI == SSDSConstants.Namespaces.Sitka)
                    {
                        id = Utils.ReadString(reader);
                    }
                    else if (reader.LocalName == "Version" && reader.NamespaceURI == SSDSConstants.Namespaces.Sitka)
                    {
                        version = int.Parse(Utils.ReadString(reader));
                    }
                }

                if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == name && reader.NamespaceURI == SSDSConstants.Namespaces.Sitka)
                {
                    return ctor(id, version);
                }

            }

            throw CodePlexException.Format("Failed to parse {0}.", name);
        }

    }
   
}
